/**
 * @file app\tasks\mobus\devices\_report_information.c
 *
 * Copyright (C) 2023
 *
 * _report_information.c is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @author HinsShum hinsshum@qq.com
 *
 * @encoding utf-8
 */

/*---------- includes ----------*/
#include "inc/mobus.h"
#include "mobus_device.h"
#include "options.h"
#include "protocol_mobus.h"
#include "resources.h"

/*---------- macro ----------*/
typedef enum {
    MODEL = 0x01,
    UPGRADE_TYPE = 0x02,
    FUNCTION_DEF = 0x03,
    VERSION = 0x04,
    SN = 0x05,
    ACTIVE_STATUS = 0x06,
    DEVUID = 0x10,
} information_type_t;

/*---------- type define ----------*/
typedef struct tlv* tlv_t;
struct tlv {
    uint8_t type;
    uint8_t length;
    uint8_t value[0];
};

/*---------- variable prototype ----------*/
/*---------- function prototype ----------*/
static uint8_t __tlv_model_make(mobus_device_t self, tlv_t tlv);
static uint8_t __tlv_upgrade_type_make(mobus_device_t self, tlv_t tlv);
static uint8_t __tlv_function_def_make(mobus_device_t self, tlv_t tlv);
static uint8_t __tlv_version_make(mobus_device_t self, tlv_t tlv);
static uint8_t __tlv_sn_make(mobus_device_t self, tlv_t tlv);
static uint8_t __tlv_active_make(mobus_device_t self, tlv_t tlv);
static uint8_t __tlv_devuid_make(mobus_device_t self, tlv_t tlv);

/*---------- variable ----------*/
static struct protocol_callback tlv_get_cbs[] = {
    [MODEL] = {strlen(VERSION_MODEL), __tlv_model_make},
    [UPGRADE_TYPE] = {1, __tlv_upgrade_type_make},
    [FUNCTION_DEF] = {1, __tlv_function_def_make},
    [VERSION] = {3, __tlv_version_make},
    [SN] = {FIELD_SIZEOF(struct storage_authinfo, sn) - 1, __tlv_sn_make},
    [ACTIVE_STATUS] = {1, __tlv_active_make},
    [DEVUID] = {26, __tlv_devuid_make},
};

/*---------- function ----------*/
static inline uint8_t __get_tlv_length_by_type(information_type_t type) {
    uint8_t length = 0;

    if (type < ARRAY_SIZE(tlv_get_cbs)) {
        length = tlv_get_cbs[type].type;
    }

    return length;
}

static uint8_t __tlv_model_make(mobus_device_t self, tlv_t tlv) {
    tlv->type = MODEL;
    tlv->length = tlv_get_cbs[MODEL].type;
    memcpy(tlv->value, VERSION_MODEL, tlv->length);

    return (tlv->length + sizeof(*tlv));
}

static uint8_t __tlv_upgrade_type_make(mobus_device_t self, tlv_t tlv) {
    tlv->type = UPGRADE_TYPE;
    tlv->length = tlv_get_cbs[UPGRADE_TYPE].type;
    tlv->value[0] = 0;

    return (tlv->length + sizeof(*tlv));
}

static uint8_t __tlv_function_def_make(mobus_device_t self, tlv_t tlv) {
    tlv->type = FUNCTION_DEF;
    tlv->length = tlv_get_cbs[FUNCTION_DEF].type;
    tlv->value[0] = 0;

    return (tlv->length + sizeof(*tlv));
}

static uint8_t __tlv_version_make(mobus_device_t self, tlv_t tlv) {
    tlv->type = VERSION;
    tlv->length = tlv_get_cbs[VERSION].type;
    tlv->value[0] = VERSION_HARDWARE;
    tlv->value[1] = strtoul(STRING(VERSION_MAJOR), NULL, 10);
    tlv->value[2] = strtoul(STRING(VERSION_MINOR), NULL, 10);

    return (tlv->length + sizeof(*tlv));
}

static uint8_t __tlv_sn_make(mobus_device_t self, tlv_t tlv) {
    struct dp_storage dp = { .type = DP_STORAGE_TYPE_AUTH };

    self->act->ops.pull(self->act, "storage", &dp, sizeof(dp));
    tlv->type = SN;
    tlv->length = strlen((char*)dp.param.auth.sn);
    if (tlv->length > tlv_get_cbs[SN].type) {
        tlv->length = tlv_get_cbs[SN].type;
    }
    memcpy(tlv->value, dp.param.auth.sn, tlv->length);

    return (tlv->length + sizeof(*tlv));
}

static uint8_t __tlv_active_make(mobus_device_t self, tlv_t tlv) {
    tlv->type = ACTIVE_STATUS;
    tlv->length = tlv_get_cbs[ACTIVE_STATUS].type;
    tlv->value[0] = 0;

    return (tlv->length + sizeof(*tlv));
}

static uint8_t __tlv_devuid_make(mobus_device_t self, tlv_t tlv) {
    uint8_t uid[12] = { 0 };

    cpu_get_uuid(uid, ARRAY_SIZE(uid));
    tlv->type = DEVUID;
    tlv->length = tlv_get_cbs[DEVUID].type;
    snprintf((char*)tlv->value, tlv->length,
        "GD%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", uid[0], uid[1],
        uid[2], uid[3], uid[4], uid[5], uid[6], uid[7], uid[8], uid[9],
        uid[10], uid[11]);

    return (tlv->length + sizeof(*tlv));
}

static void _report_information(mobus_device_t self, uint8_t channel,
    uint8_t* param, uint8_t length) {
    uint8_t wanted_size = 1;
    uint8_t off = 0;
    uint8_t* p = NULL;
    uint8_t* tlv = NULL;
    struct dp_storage dp = { .type = DP_STORAGE_TYPE_AUTH };
    uint8_t(*cb)(mobus_device_t, tlv_t) = NULL;

    do {
        (void)channel;
        for (uint8_t i = 0; i < length; ++i) {
            wanted_size += __get_tlv_length_by_type((information_type_t)param[i]);
        }
        if (!wanted_size) {
            break;
        }
        p = __malloc(wanted_size);
        if (!p) {
            break;
        }
        tlv = p;
        do {
            if (param[off] < ARRAY_SIZE(tlv_get_cbs)) {
                cb = tlv_get_cbs[param[off]].cb;
                if (cb) {
                    tlv += cb(self, (tlv_t)tlv);
                }
            }
            off++;
        } while (off < length);
        protocol_mobus_upload(MOBUS_FRAME_TYPE_UPLOAD_AUTHINFO,
            self->native_address, 0xF, 0xF, p, tlv - p);
        __free(p);
    } while (0);
}
